﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Rogue
{
    public class BodyPart
    {
        protected List<BodyPart> m_Children;     // list of child parts. A child is something you'd lose if the bodypart was destroyed
        public List<BodyPart> Children          // should this be readonly?
        { get { return m_Children; } }

        protected BodyPart m_Parent;             // we're in their children list. Null if we're separated from them.
        public BodyPart Parent
        { get { return m_Parent; } }

        public bool m_DrawWhenDead;

        public int m_AttackIndex;
        public void InitAttackIndices()
        {
            int i = 1;
            foreach (BodyPart c in GetAllParts<BodyPart>())
            {
                c.m_AttackIndex = i;
                ++i;
            }
        }


        protected Entity m_Owner;   // only defined for the root bodypart. Recursive access for non-root bodyparts
        public Entity Owner
        {
            get
            {
                if (m_Parent != null)
                    return m_Parent.Owner;
                else
                    return m_Owner;
            }

            set
            {
                if (m_Parent != null)
                    m_Parent.Owner = value;
                else
                    m_Owner = value;
            }
        }

        protected List<BodyPart> m_LostChildren;
        public List<BodyPart> LostChildren
        { get { return m_LostChildren; } }

        protected Attack m_Attack;  // possibly null if this limb doesn't grant us an attack
        public Attack GrantedAttack
        {
            get { return m_Attack; }
            set { m_Attack = value; }
        }

        // Totally arbitrary names for flavor, don't look up bodyparts based on these names.
        // eg. "left" (for arms), "pinky" (for fingers), null if it's unique (for the head, torso)
        protected string m_Location;
        public string GetLocationAndName()
        {
            if (m_Location != null)
            {
                return String.Format("{0} {1}", m_Location, GetType().Name);
            }
            else
            {
                return GetType().Name;
            }
        }

        const int defaultPartHealth = 20;

        // constructors that take fewer parameters, for convenience.
        public BodyPart(Entity a_Owner)
            : this(a_Owner, null, null, defaultPartHealth)
        {
        }

        public BodyPart(BodyPart a_parent)
            : this(null, a_parent, null, defaultPartHealth)
        {
        }

        public BodyPart(BodyPart a_parent, string a_Location)
            : this(null, a_parent, a_Location, defaultPartHealth)
        {
        }

        public BodyPart(Entity a_Owner, BodyPart a_parent, string a_Location, int a_MaxHealth)
        {
            m_Children = new List<BodyPart>();
            m_LostChildren = new List<BodyPart>();
            m_Parent = a_parent;
            m_Owner = a_Owner;
            m_Location = a_Location;
            m_DrawWhenDead = true;

            Debug.Assert(m_Owner != null || m_Parent != null, "Should either have a bodypart parent or an entity owner!");

            m_BleedingLeft = 0.0f;

            m_Health = m_MaxHealth = a_MaxHealth;

            if (a_parent != null)
            {
                a_parent.m_Children.Add(this);
                m_Parent = a_parent;
            }
        }

        void Sever()
        {
            if (m_Parent != null)
            {
                // notify the parent that we're gone
                m_Parent.m_Children.Remove(this);
                m_Parent.m_LostChildren.Add(this);  // we should be adding a clone... :( What if someone else modifies the lost limb...
                m_Parent = null;
            }
        }

        public void MakeSeveredPart(Entity a_Owner)
        {
            SeveredBodyPart sbp = new SeveredBodyPart(this, a_Owner);
            sbp.Position = a_Owner.Position;
        }

        #region health and damage states
        protected int m_Health;           // when this goes to zero, the limb is lost. This doesn't include the health of children.
        protected int m_MaxHealth;

        public int GetHealth()  // doesn't include children
        {
            return m_Health;
        }

        public int GetTotalHealth() // includes children. Lost limbs count as 0 health.
        {
            int total = 0;
            foreach (BodyPart c in GetAllParts<BodyPart>())
                total += c.GetHealth();
            return total;
        }

        // Does GetTotalMaxHealth make sense? It includes the health of lost limbs!
        public int GetTotalMaxHealth(bool a_CountLostLimbs)
        {
            int total = 0;
            foreach (BodyPart c in GetAllParts<BodyPart>())
                total += c.GetMaxHealth();

            if (a_CountLostLimbs)
                foreach (BodyPart c in GetAllLostParts<BodyPart>())
                    total += c.GetMaxHealth();

            return total;
        }

        public void MultMaxHealth(float a_Mult)
        {
            foreach (BodyPart c in GetAllParts<BodyPart>())
            {
                c.m_Health = (int)(c.m_Health * a_Mult);
                c.m_MaxHealth = (int)(c.m_MaxHealth * a_Mult);
            }
        }

        public int GetMaxHealth()
        {
            return m_MaxHealth;
        }

        public enum HealthChangeType
        {
            Heal,
            Damage,
            Bleed
        }

        // Negative means damage (or bleeding), positive means healing.
        // Note! It's also called for every ancestor of that bodypart!
        public delegate void HealthChangeHandler(BodyPart a_Part, HealthChangeType a_ChangeType, int a_DeltaHealth);
        public event HealthChangeHandler OnSubPartHealthChange;

        // This event fires after a bodypart or one of its children is lost. 
        // Note! It's also called for every ancestor of that bodypart!
        // So you can just put a callback on the root and it gets hit for every severed child.
        public delegate void PartLossHandler(BodyPart a_Part);
        public event PartLossHandler OnSubPartLoss;



        // TODO: damage types, armor, sub-part selection?

        public virtual void TakeDamage(Attack a_Attack)
        {
            int damage = a_Attack.GetTotalDamage();

            if (a_Attack.m_Type == DamageType.Cut)
            {
                int bleedDamage = (int)(CutDamageBleedFraction * damage);
                m_BleedingLeft += bleedDamage;
                damage -= bleedDamage;
            }

            m_Health = Math.Max(0, m_Health - damage);

            if (damage > 0)
            {
                DetailWindow.FocusBoth(this.Owner, true);
            }

            BodyPart ancestor = this;
            while (ancestor != null)
            {
                if (ancestor.OnSubPartHealthChange != null)     // found one, call it
                    ancestor.OnSubPartHealthChange(this, HealthChangeType.Damage, -damage);

                ancestor = ancestor.m_Parent;   // go up to the next parent
            }

            if (m_Health <= 0)
                HandleDeath();
        }

        // call this when m_Health <= 0
        private void HandleDeath()
        {
            BodyPart parentBeforeSever = m_Parent;
            Sever();

            if (OnSubPartLoss != null)
                OnSubPartLoss(this);

            // call all the part-loss callbacks for ancestors. 
            // (we remember the parent before calling sever! sever loses them)
            BodyPart ancestor = parentBeforeSever;
            while (ancestor != null)
            {
                // found one, call it
                if (ancestor.OnSubPartLoss != null)
                    ancestor.OnSubPartLoss(this);

                // go up to the next parent
                ancestor = ancestor.m_Parent;
            }
        }

        // We also handle the bleed updating here, healing over time, etc.
        // TODO: parameters for heal rates and such.
        public void UpdateSubpartsHealth(int a_TimeElapsed)
        {
            if (a_TimeElapsed <= 0)
                return;

            // need to make a temporary copy to iterate over, in case of limb loss
            List<BodyPart> limbs = new List<BodyPart>(GetAllParts<BodyPart>());
            foreach (BodyPart p in limbs)
            {
                p.UpdateHealth(a_TimeElapsed);
            }
        }

        public float BleedingLeft()
        {
            return m_BleedingLeft;
        }

        protected float m_BleedingLeft;      // deferred damage

        // fractional parts get a random chance of healing. So a heal amount of 3.3
        // means you get 3 points for sure, and a 30% chance of a 4th point.
        // Makes it easier to tweak the rates without worrying about rounding.
        const float BleedDamageAppliedPerTick = 0.005f;
        const float HealPerTime = 0.005f;    // in HP per tick. Should be per-entitydef...

        const float CutDamageBleedFraction = 0.8f;      // what fraction of cut damage gets deferred to bleeding. Should weapons do multiple damage types?

        public void UpdateHealth(int a_TimeElapsed)
        {
            if (m_BleedingLeft <= 0 && m_Health >= m_MaxHealth)
                return;

            float healAmount = HealPerTime * a_TimeElapsed;

            float healOrBleedAmount = 0;    // positive if we've healed, negative if we've bled
            HealthChangeType type = HealthChangeType.Heal;

            // If we're bleeding, our healing goes towards reducing bleed damage.
            if (m_BleedingLeft > 0)
            {
                // TODO: poisson distribution or something? This is hacky...
                for (int i = 0; i < a_TimeElapsed; ++i)
                {
                    float thisTick = BleedDamageAppliedPerTick * m_BleedingLeft;
                    healOrBleedAmount -= thisTick;
                    m_BleedingLeft -= thisTick;
                }

                type = HealthChangeType.Bleed;

                m_BleedingLeft = Math.Max(0, m_BleedingLeft - healAmount);
            }
            else if (m_Health < m_MaxHealth)    // otherwise it increases our health (up to the max)
            {
                healOrBleedAmount = Math.Min(m_MaxHealth - m_Health, HealPerTime * a_TimeElapsed);
            }

            int intPart = (int)healOrBleedAmount;
            float fracPart = healOrBleedAmount - intPart;
            int randomPart = Random.Instance.Next() < fracPart ? 1 : 0;
            if (fracPart < 0)
                randomPart = Random.Instance.Next() < -fracPart ? -1 : 0;

            // don't need to cap it at max - healing is 0 if we're >= max health
            m_Health = Math.Max(0, m_Health + intPart + randomPart);

            // health change callbacks
            BodyPart ancestor = this;
            while (ancestor != null)
            {
                if (ancestor.OnSubPartHealthChange != null)     // found one, call it
                    ancestor.OnSubPartHealthChange(this, type, intPart + randomPart);

                ancestor = ancestor.m_Parent;   // go up to the next parent
            }

            if (m_Health <= 0)
                HandleDeath();
        }

        // a little array that's a map of of health numbers -> color
        protected class ColorAtHealthFrac
        {
            public ColorAtHealthFrac(Vector3 a_Col, float a_Frac)
            {
                m_Color = a_Col;
                m_HealthFrac = a_Frac;
            }

            public Vector3 m_Color;
            public float m_HealthFrac;
        };

        public static Color GetDefaultColorFromHealth(float a_HealthFrac)
        {
            return GetColorFromHealth(a_HealthFrac, s_DefaultDamageColors);
        }

        protected static Color GetColorFromHealth(float a_Health, ColorAtHealthFrac[] a_Damages)
        {
            ColorAtHealthFrac healthier = a_Damages[0];
            ColorAtHealthFrac moreDamaged = a_Damages[a_Damages.Length - 1];

            for (int i = 0; i < a_Damages.Length; ++i)
            {
                // pick the healthier color. It has to be healthier than us,
                // and has to be less healthy than the previous best pick
                if (a_Health <= a_Damages[i].m_HealthFrac &&
                    moreDamaged != a_Damages[i] &&
                    a_Damages[i].m_HealthFrac < healthier.m_HealthFrac)
                {
                    healthier = a_Damages[i];
                }

                // pick the more damaged color if it's better than what we had
                if (a_Health >= a_Damages[i].m_HealthFrac &&
                    healthier != a_Damages[i] &&
                    a_Damages[i].m_HealthFrac > moreDamaged.m_HealthFrac)
                {
                    moreDamaged = a_Damages[i];
                }
            }

            // interpolate between those closest two colors in the array
            float spread = healthier.m_HealthFrac - moreDamaged.m_HealthFrac;
            if (spread == 0.0f)
                return new Color(moreDamaged.m_Color);

            float lerp = (a_Health - moreDamaged.m_HealthFrac) / spread;  // 0 = next, 1 = prev
            Vector3 result = healthier.m_Color * lerp + moreDamaged.m_Color * (1.0f - lerp);

            if (result.X > 1)
                result /= result.X;
            if (result.Y > 1)
                result /= result.Y;
            if (result.Z > 1)
                result /= result.Z;

            return new Color(result);
        }

        // make one of these arrays in your base class if you want to override health colors.
        // Then overload GetColorFromHealth to check your new array.
        static ColorAtHealthFrac[] s_DefaultDamageColors = 
        {
            new ColorAtHealthFrac(new Vector3(1, 1, 1),             1.0f ),
            new ColorAtHealthFrac(new Vector3(1.0f, 0.75f, 0.4f),   0.8f ),
            new ColorAtHealthFrac(Color.Maroon.ToVector3(),         0.0f ),
        };

        public virtual Color GetColorFromHealth()
        {
            return GetColorFromHealth((float)GetHealth() / GetMaxHealth(), s_DefaultDamageColors);
        }

        public virtual string DescribeCurrHealth()
        {
            float healthFrac = (float)GetHealth() / GetMaxHealth();

            string ret = null;

            if (healthFrac > 1.01f)
            {
                ret = "incredible";
            }
            else if (healthFrac > 0.95f)
            {
                ret = "healthy";
            }
            else if (healthFrac > 0.6f)
            {
                ret = "sore";
            }
            else if (healthFrac > 0.3f)
            {
                ret = "wounded";
            }
            else if (healthFrac > 0.0f)
            {
                ret = "pulped";
            }
            else
            {
                ret = "destroyed";
            }

            if (healthFrac > 0.0f)
            {
                const float seriousBleedFrac = 0.3f;
                const float lightBleedFrac = 0.1f;
                if (m_BleedingLeft > seriousBleedFrac * GetMaxHealth())
                {
                    ret += " and bleeding heavily";
                }
                else if (m_BleedingLeft > lightBleedFrac * GetMaxHealth())
                {
                    ret += " and bleeding";
                }
            }

            return ret;
        }

        #endregion


        #region available actions
        // if we ever get an Action class, replace these methods with a list of actions per bodypart...
        // eg. List<EntityAction> m_AvailableActions;    // hands pick up and attack, legs move, snake torso can move, spider torso spins web, etc

        // do we need a CarnivoreHead to attack with a head? or just give carnivores attacky mouths? or a subtype of leg to attack with legs?
        // the List<EntityAction> would handle this, since only horse-ish entities would get the HorseKick action on their plain old Leg parts
        public virtual bool CanAttack()
        {
            return false;
        }

        //bool CanWield(Item a_Item)
        //{
        //    return false;
        //}

        // float GetMovementSpeed()     // since we want people to drag themselves around with arms, or go slower with damaged legs.
        // maybe divide this by their total weight? affected by overall damage?
        public virtual bool CanMoveWith()
        {
            return false;
        }
        #endregion

        #region child enumators
        public IEnumerable<T> GetAllParts<T>() where T : BodyPart
        {
            if (this is T)
                yield return (T)this;

            foreach (BodyPart child in m_Children)
            {
                foreach (T grandChild in child.GetAllParts<T>())
                    yield return grandChild;
            }
        }

        public bool HasParts<T>() where T : BodyPart
        {
            foreach (T child in GetAllParts<T>())
                return true;

            return false;
        }

        public int GetNumParts<T>() where T : BodyPart
        {
            int count = 0;
            foreach (T child in GetAllParts<T>())
                ++count;

            return count;
        }

        // for looking at lost limbs...
        public IEnumerable<T> GetAllLostParts<T>() where T : BodyPart
        {
            foreach (BodyPart child in m_Children)
            {
                foreach (T grandChild in child.GetAllLostParts<T>())
                    yield return grandChild;
            }

            foreach (BodyPart child in m_LostChildren)
            {
                if (child is T)
                    yield return (T)child;

                foreach (T grandChild in child.GetAllLostParts<T>())
                    yield return grandChild;
            }
        }
        #endregion
    };

    // common base classes, for setting up a body.
    public class Head : BodyPart
    {
        public Head(BodyPart a_Parent)
            : base(a_Parent)
        {
            m_Health = m_MaxHealth = 25;
        }
    };

    public class Torso : BodyPart
    {
        public Torso(Entity a_Owner)
            : base(a_Owner)
        {
            Owner = a_Owner;
            m_Health = m_MaxHealth = 50;
        }
    };

    public class Leg : BodyPart
    {
        public Leg(BodyPart a_Parent, string a_Location)
            : base(a_Parent, a_Location)
        {
        }

        public override bool CanMoveWith()
        {
            return true;
        }
    }

    public class Arm : BodyPart
    {
        public Arm(BodyPart a_Parent, string a_Location)
            : base(a_Parent, a_Location)
        {
        }

        public override bool CanAttack()
        {
            return true;
        }
    };

    // more subtypes, like an eye? Or should those be faked just in the damage callbacks?
    // 
}
